package drds.binlog.parse.mysql.ddl;

import com.alibaba.fastsql.sql.SQLUtils;
import com.alibaba.fastsql.sql.ast.SQLExpr;
import com.alibaba.fastsql.sql.ast.SQLStatement;
import com.alibaba.fastsql.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.fastsql.sql.ast.statement.*;
import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement;
import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item;
import com.alibaba.fastsql.sql.parser.ParserException;
import com.alibaba.fastsql.util.JdbcConstants;
import drds.binlog.common.Authors;
import drds.binlog.data_object.EventType;
import drds.common.$;
import drds.common.Author;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


@Author(name = Authors.LI_YANG)
public class FastsqlDdlParser
{

    public static List<Ddl> parse(String queryString, String schmeaName)
    {
        List<SQLStatement> sqlStatementList = null;
        try
        {
            sqlStatementList = SQLUtils.parseStatements(queryString, JdbcConstants.MYSQL, false);
        } catch (ParserException e)
        {
            // 可能存在一些SQL是不支持的，比如存储过程
            Ddl ddl = new Ddl();
            ddl.setEventType(EventType.query);
            return Arrays.asList(ddl);
        }

        List<Ddl> ddlList = new ArrayList<Ddl>();
        for (SQLStatement statement : sqlStatementList)
        {
            if (statement instanceof SQLCreateTableStatement)
            {
                Ddl ddl = new Ddl();
                SQLCreateTableStatement sqlCreateTableStatement = (SQLCreateTableStatement) statement;
                processName(ddl, schmeaName, sqlCreateTableStatement.getName(), false);
                ddl.setEventType(EventType.create);
                ddlList.add(ddl);
            } else if (statement instanceof SQLAlterTableStatement)
            {
                SQLAlterTableStatement sqlAlterTableStatement = (SQLAlterTableStatement) statement;
                for (SQLAlterTableItem sqlAlterTableItem : sqlAlterTableStatement.getItems())
                {
                    if (sqlAlterTableItem instanceof SQLAlterTableRename)
                    {
                        Ddl ddl = new Ddl();
                        processName(ddl, schmeaName, sqlAlterTableStatement.getName(), true);
                        processName(ddl, schmeaName, ((SQLAlterTableRename) sqlAlterTableItem).getToName(), false);
                        ddlList.add(ddl);
                    } else if (sqlAlterTableItem instanceof SQLAlterTableAddIndex)
                    {
                        Ddl ddl = new Ddl();
                        processName(ddl, schmeaName, sqlAlterTableStatement.getName(), false);
                        ddl.setEventType(EventType.create_index);
                        ddlList.add(ddl);
                    } else if (sqlAlterTableItem instanceof SQLAlterTableDropIndex || sqlAlterTableItem instanceof SQLAlterTableDropKey)
                    {
                        Ddl ddl = new Ddl();
                        processName(ddl, schmeaName, sqlAlterTableStatement.getName(), false);
                        ddl.setEventType(EventType.drop_index);
                        ddlList.add(ddl);
                    } else if (sqlAlterTableItem instanceof SQLAlterTableAddConstraint)
                    {
                        Ddl ddl = new Ddl();
                        processName(ddl, schmeaName, sqlAlterTableStatement.getName(), false);
                        SQLConstraint constraint = ((SQLAlterTableAddConstraint) sqlAlterTableItem).getConstraint();
                        if (constraint instanceof SQLUnique)
                        {
                            ddl.setEventType(EventType.create_index);
                            ddlList.add(ddl);
                        }
                    } else if (sqlAlterTableItem instanceof SQLAlterTableDropConstraint)
                    {
                        Ddl ddl = new Ddl();
                        processName(ddl, schmeaName, sqlAlterTableStatement.getName(), false);
                        ddl.setEventType(EventType.drop_index);
                        ddlList.add(ddl);
                    } else
                    {
                        Ddl ddl = new Ddl();
                        processName(ddl, schmeaName, sqlAlterTableStatement.getName(), false);
                        ddl.setEventType(EventType.alter);
                        ddlList.add(ddl);
                    }
                }
            } else if (statement instanceof SQLDropTableStatement)
            {
                SQLDropTableStatement sqlDropTableStatement = (SQLDropTableStatement) statement;
                for (SQLExprTableSource tableSource : sqlDropTableStatement.getTableSources())
                {
                    Ddl ddl = new Ddl();
                    processName(ddl, schmeaName, tableSource.getExpr(), false);
                    ddl.setEventType(EventType.erase);
                    ddlList.add(ddl);
                }
            } else if (statement instanceof SQLCreateIndexStatement)
            {
                SQLCreateIndexStatement sqlCreateIndexStatement = (SQLCreateIndexStatement) statement;
                SQLTableSource tableSource = sqlCreateIndexStatement.getTable();
                Ddl ddl = new Ddl();
                processName(ddl, schmeaName, ((SQLExprTableSource) tableSource).getExpr(), false);
                ddl.setEventType(EventType.create_index);
                ddlList.add(ddl);
            } else if (statement instanceof SQLDropIndexStatement)
            {
                SQLDropIndexStatement sqlDropIndexStatement = (SQLDropIndexStatement) statement;
                SQLExprTableSource tableSource = sqlDropIndexStatement.getTableName();
                Ddl ddl = new Ddl();
                processName(ddl, schmeaName, tableSource.getExpr(), false);
                ddl.setEventType(EventType.drop_index);
                ddlList.add(ddl);
            } else if (statement instanceof SQLTruncateStatement)
            {
                SQLTruncateStatement sqlTruncateStatement = (SQLTruncateStatement) statement;
                for (SQLExprTableSource tableSource : sqlTruncateStatement.getTableSources())
                {
                    Ddl ddl = new Ddl();
                    processName(ddl, schmeaName, tableSource.getExpr(), false);
                    ddl.setEventType(EventType.truncate);
                    ddlList.add(ddl);
                }
            } else if (statement instanceof MySqlRenameTableStatement)
            {
                MySqlRenameTableStatement mySqlRenameTableStatement = (MySqlRenameTableStatement) statement;
                for (Item item : mySqlRenameTableStatement.getItems())
                {
                    Ddl ddl = new Ddl();
                    processName(ddl, schmeaName, item.getName(), true);
                    processName(ddl, schmeaName, item.getTo(), false);
                    ddl.setEventType(EventType.rename);
                    ddlList.add(ddl);
                }
            } else if (statement instanceof SQLInsertStatement)
            {
                Ddl ddl = new Ddl();
                SQLInsertStatement sqlInsertStatement = (SQLInsertStatement) statement;
                processName(ddl, schmeaName, sqlInsertStatement.getTableName(), false);
                ddl.setEventType(EventType.insert);
                ddlList.add(ddl);
            } else if (statement instanceof SQLUpdateStatement)
            {
                Ddl ddl = new Ddl();
                SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) statement;
                // 拿到的表名可能为null,比如update a,b set a.id=x
                processName(ddl, schmeaName, sqlUpdateStatement.getTableName(), false);
                ddl.setEventType(EventType.update);
                ddlList.add(ddl);
            } else if (statement instanceof SQLDeleteStatement)
            {
                Ddl ddl = new Ddl();
                SQLDeleteStatement sqlDeleteStatement = (SQLDeleteStatement) statement;
                // 拿到的表名可能为null,比如delete a,b from a where a.id = b.id
                processName(ddl, schmeaName, sqlDeleteStatement.getTableName(), false);
                ddl.setEventType(EventType.delete);
                ddlList.add(ddl);
            } else if (statement instanceof SQLCreateDatabaseStatement)
            {
                Ddl ddl = new Ddl();
                ddl.setEventType(EventType.query);
                // 只设置schema
                processName(ddl, schmeaName, null, false);
                ddlList.add(ddl);
            } else if (statement instanceof SQLDropDatabaseStatement)
            {
                Ddl ddl = new Ddl();
                ddl.setEventType(EventType.query);
                // 只设置schema
                processName(ddl, schmeaName, null, false);
            }
        }

        return ddlList;
    }

    private static void processName(Ddl ddl, String schema, SQLExpr sqlName, boolean isOri)
    {
        if (sqlName == null)
        {
            if ($.isNotNullAndNotEmpty(schema))
            {
                ddl.setSchemaName(schema);
            }
            return;
        }

        String table = null;
        if (sqlName instanceof SQLPropertyExpr)
        {
            SQLIdentifierExpr owner = (SQLIdentifierExpr) ((SQLPropertyExpr) sqlName).getOwner();
            schema = unescapeName(owner.getName());
            table = unescapeName(((SQLPropertyExpr) sqlName).getName());
        } else if (sqlName instanceof SQLIdentifierExpr)
        {
            table = unescapeName(((SQLIdentifierExpr) sqlName).getName());
        }

        if (isOri)
        {
            ddl.setOriginalSchemaName(schema);
            ddl.setOriginalTableName(table);
        } else
        {
            ddl.setSchemaName(schema);
            ddl.setTableName(table);
        }
    }

    public static String unescapeName(String name)
    {
        if (name != null && name.length() > 2)
        {
            char c0 = name.charAt(0);
            char x0 = name.charAt(name.length() - 1);
            if ((c0 == '"' && x0 == '"') || (c0 == '`' && x0 == '`'))
            {
                return name.substring(1, name.length() - 1);
            }
        }

        return name;
    }

    public static String unescapeQuotaName(String name)
    {
        if (name != null && name.length() > 2)
        {
            char c0 = name.charAt(0);
            char x0 = name.charAt(name.length() - 1);
            if (c0 == '\'' && x0 == '\'')
            {
                return name.substring(1, name.length() - 1);
            }
        }

        return name;
    }

}
